/*
 * MIT License
 *
 * Copyright (c) 2020 wen.gu <454727014@qq.sys>
 *
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

 /***************************************************************************
 * Name: ipc_packet.cpp
 *
 * Purpose: ipc packet parser implementation for 'COM' Functional.
 *
 * Developer:
 *   wen.gu , 2020-07-27
 *
 * TODO:
 *
 ***************************************************************************/

 /******************************************************************************
 **    INCLUDES
 ******************************************************************************/
#include "panda/sys/ipc_packet.h"

#include "panda/sys/serializer.h"
#define LOG_TAG "IPKT"
#include "panda/core/log.h"

namespace panda
{
namespace sys
{

/******************************************************************************
 **    MACROS
 ******************************************************************************/
/**|size(4)|sessionId(4)|type(1)|nameSize(1) */
#define PKT_WRAPPER_SIZE  10
/******************************************************************************
 **    VARIABLE DEFINITIONS
 ******************************************************************************/



/******************************************************************************
 **    FUNCTION DEFINITIONS
 ******************************************************************************/

IpcDataParser::IpcDataParser(DataReceiveHandler drh)
    :mHandler(drh)
{
    /** todo something */
}

IpcDataParser::~IpcDataParser()
{
    
}


IpcDataParser::PandaErrc IpcDataParser::doParse(IpcPacketInfo& ici, const uint8_t* pdata, uint32_t size)
{
    const uint8_t* pData = (uint8_t*)pdata;
    const uint8_t* pEnd = pdata + size;
    //LOGD("===>: size: %d\n", size);
    while (pData < pEnd)
    {
        switch (ici.state)
        {
        case IpcDataParseState::Start:
            //LOGD("===>: size: %d, start: 0x%x\n", size, *pData);
            pData = onParseStart(ici, pData, pEnd);
            break;
        case IpcDataParseState::Header:
            //LOGD("===>: size: %d, start: 0x%x\n", size, *pData);
            pData = onParseHeader(ici, pData, pEnd);
            break; 
        case IpcDataParseState::Payload:
            pData = onParsePayload(ici, pData, pEnd);
            //LOGD("===>: size: %d\n", size);
            break;
        case IpcDataParseState::End:
            pData = onParseEnd(ici, pData, pEnd);
            //LOGD("===>: size: %d\n", size);
            break;
        default:
            //LOGD("===>: size: %d\n", size);
            break;
        }
    }

    return PandaErrc::OK; /** todo, refineme */
    //LOGD("===>: size: %d\n", size);   
}

IpcPacketData IpcDataParser::makePacket(const IpcPacketParam& ipp, const IpcPacketData& data)
{
    return makePacket(ipp, data.payload.get(), data.size);
}

IpcPacketData IpcDataParser::makePacket(const IpcPacketParam& ipp, const uint8_t* data, uint32_t size)
{
    /** the protocol of ipc data packet:
     * |start code|size|sessionId|type|nameSize|name|payload content|end code|
     *
     * start code:      1 byte
     * size:            4 bytes(include sessionId,type,name and payload content)
     * sessionId:       4 bytes
     * type:            1 byte
     * nameSize:        1 byte
     * name:            n bytes(variable length, max limit:256)
     * payload content: n bytes(variable length)
     * end code:        1 byte
     *
     */
    uint32_t nameSize = (uint32_t)ipp.name.size();
    uint32_t pktSize = PKT_WRAPPER_SIZE + nameSize + size;
    uint32_t totalSize = pktSize + 2; /** +2: 1 byte start code, 1 byte end code */
    PayloadPtr pData = MakePayloadPtr(totalSize);
    uint8_t* pBuf = pData.get();
    *pBuf++ = IPC_PKT_START_CODE;
    memcpy(pBuf, &pktSize, sizeof(pktSize));
    pBuf += sizeof(pktSize);

    memcpy(pBuf, &ipp.sessionId, sizeof(ipp.sessionId));
    pBuf += sizeof(ipp.sessionId);
    *pBuf++ = (uint8_t)ipp.type;
    *pBuf++ = (uint8_t)nameSize;

    if (nameSize > 0)
    {
        memcpy(pBuf, ipp.name.data(), (uint8_t)nameSize);
        pBuf += (uint8_t)nameSize;
    }

    if (data && size > 0)
    {
        memcpy(pBuf, data, size);
        pBuf += size;
    }

    *pBuf = IPC_PKT_END_CODE;

    IpcPacketData outPkt;
    outPkt.payload = pData;
    outPkt.size = totalSize;
    return outPkt;
}


const uint8_t* IpcDataParser::onParseStart(IpcPacketInfo& ici, const uint8_t* pdata, const uint8_t* pend)
{
    while (pdata < pend)
    {
        if (*pdata == IPC_PKT_START_CODE)
        {
            pdata++;

            //ici.readPos = 0;
            ici.headerReadPos = 0;
            ici.state = IpcDataParseState::Header;
            break;
        }

        pdata++;
    }

    return pdata;
}


const uint8_t* IpcDataParser::onParseHeader(IpcPacketInfo& ici, const uint8_t* pdata, const uint8_t* pend)
{    
    int32_t size = (int32_t)(pend - pdata);
    if (ici.headerReadPos < IPC_PKT_HDR_SIZE)
    {
        
        int32_t cpSize = IPC_PKT_HDR_SIZE - ici.headerReadPos;
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(&(ici.header[ici.headerReadPos]), pdata, cpSize);
        ici.headerReadPos += cpSize;
        pdata += cpSize;
        size -= cpSize;
    }

    if (ici.headerReadPos == IPC_PKT_HDR_SIZE)
    {
        memcpy(&(ici.len), ici.header, sizeof(ici.len)); /** get total size */
        memcpy(&(ici.sessionId), &(ici.header[sizeof(ici.len)]), sizeof(ici.sessionId));
        ici.type = (MessageType)ici.header[sizeof(ici.len) + sizeof(ici.sessionId)];
        ici.nameSize = ici.header[sizeof(ici.len) + sizeof(ici.sessionId) + 1];
    }

    if (size > 0)
    {/** read pkt name */
        int32_t cpSize = (IPC_PKT_HDR_SIZE + ici.nameSize) - (int32_t)ici.headerReadPos;
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(&(ici.header[ici.headerReadPos]), pdata, cpSize);
        pdata += cpSize;
    }

    if (ici.headerReadPos == (IPC_PKT_HDR_SIZE + ici.nameSize))
    {   
        ici.payloadSize = ici.len - IPC_PKT_HDR_SIZE - ici.nameSize;
        ici.payload = MakePayloadPtr(ici.payloadSize);

        ici.state = IpcDataParseState::Payload;
    }

    return pdata;
}

const uint8_t* IpcDataParser::onParsePayload(IpcPacketInfo& ici, const uint8_t* pdata, const uint8_t* pend)
{    
    if (ici.readPos < ici.payloadSize)
    {   
        int32_t size = (int32_t)(pend - pdata);
        int32_t cpSize = (int32_t)(ici.payloadSize - ici.readPos);
        cpSize = (cpSize <= size) ? cpSize : size;
        memcpy(ici.payload.get() + ici.readPos, pdata, cpSize);
        ici.readPos += cpSize;
        pdata += cpSize;
    }

    if (ici.readPos == ici.payloadSize)
    {
        ici.state = IpcDataParseState::End;
    }

    return pdata;
}

const uint8_t* IpcDataParser::onParseEnd(IpcPacketInfo& ici, const uint8_t* pdata, const uint8_t* pend)
{
    //LOGD("===>: size: %d\n", size);
    if (*pdata == IPC_PKT_END_CODE)
    {
        pdata++;
        /** the protocol of ipc data packet:
         * |start code|size|sessionId|type|nameSize|name|payload content|end code|
         *
         * start code:      1 byte
         * size:            4 bytes(include sessionId,type,name and payload content)
         * sessionId:       4 bytes
         * type:            1 byte
         * nameSize:        1 bytes
         * name:            n bytes(variable length, max limit: 256)
         * payload content: n bytes(variable length)
         * end code:        1 byte
         *
         */
        MessagePtr msg = MakeMessagePtr();

        msg->clientId = ici.clientId;
        msg->name = std::string((const char*)&(ici.header[IPC_PKT_HDR_SIZE]), ici.nameSize);
        msg->payload = ici.payload;
        msg->payloadSize = ici.payloadSize;
        msg->sessionId = ici.sessionId;
        msg->type = ici.type;

        ici.payload = nullptr;

        if (mHandler)
        {
            mHandler(msg);
        }
    }
    //LOGD("===>: size: %d\n", size);

    ici.state = IpcDataParseState::Start;
    return pdata;
}


} /** namespace sys */
} /** namespace panda */


